/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "napi_x509_cert_selector.h"

#include <algorithm>
#include <string>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
#include "cf_log.h"
#include "cf_memory.h"
#include "utils.h"
#include "cf_result.h"
#include "cf_object_base.h"
#include "napi_cert_defines.h"
#include "napi_cert_utils.h"

namespace OHOS {
namespace CertFramework {
thread_local napi_ref NapiX509CertSelector::classRef_ = nullptr;

struct CfCtx {
    AsyncType asyncType = ASYNC_TYPE_CALLBACK;
    napi_ref callback = nullptr;
    napi_deferred deferred = nullptr;
    napi_async_work asyncWork = nullptr;

    NapiX509CertSelector *ccvClass = nullptr;
    HcfX509CertSelector *selectorData = nullptr;

    int32_t errCode = 0;
    const char *errMsg = nullptr;
};

NapiX509CertSelector::NapiX509CertSelector(HcfX509CertSelector *selector)
{
    this->x509CertSelector_ = selector;
}

NapiX509CertSelector::~NapiX509CertSelector()
{
    CfObjDestroy(this->x509CertSelector_);
}

napi_value NapiX509CertSelector::CreateX509CertSelector(napi_env env, napi_callback_info info)
{
    LOGI("start to create X509 cert selector.");

    HcfX509CertSelector *X509CertSelector = nullptr;
    CfResult res = HcfX509CertSelectorCreate(&X509CertSelector);
    if (res != CF_SUCCESS) {
        napi_throw(env, CertGenerateBusinessError(env, res, "create X509 cert selector failed"));
        LOGE("Failed to create c X509 cert selector.");
        return nullptr;
    }

    napi_value constructor = nullptr;
    napi_value instance = nullptr;
    napi_get_reference_value(env, classRef_, &constructor);
    napi_new_instance(env, constructor, 0, nullptr, &instance);
    NapiX509CertSelector *ccvClass = new (std::nothrow) NapiX509CertSelector(X509CertSelector);
    if (ccvClass == nullptr) {
        napi_throw(env, CertGenerateBusinessError(env, CF_ERR_MALLOC, "Failed to create a ccv class"));
        LOGE("Failed to create a ccv class");
        CfObjDestroy(X509CertSelector);
        return nullptr;
    }
    napi_wrap(
        env, instance, ccvClass,
        [](napi_env env, void* data, void *hint) {
            NapiX509CertSelector *ccv = static_cast<NapiX509CertSelector *>(data);
            delete ccv;
        },
        nullptr,
        nullptr);

    return instance;
}

HcfX509CertSelector *NapiX509CertSelector::GetCertSelector(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    NapiX509CertSelector * napiSelector = nullptr;
    napi_unwrap(env, thisVar, reinterpret_cast<void **>(&napiSelector));
    if (napiSelector == nullptr) {
        LOGE("napiSelector is nullptr!");
        return nullptr;
    }
    return napiSelector->GetX509CertSelector(); 
}

napi_value NapiX509CertSelector::GetRhs(napi_env env, napi_callback_info info)
{
    size_t argc = ARGS_SIZE_ONE;
    napi_value argv[ARGS_SIZE_ONE] = { nullptr };
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
    if (!CertCheckArgsCount(env, argc, ARGS_SIZE_ONE, true)) {
        return nullptr;
    }
    return argv[PARAM0];
}

napi_value NapiX509CertSelector::GetSubjectAlternativeNames(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    //TODO
    return nullptr;
}

napi_value NapiX509CertSelector::GetMatchAllSubjectAltNames(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("selector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    bool ret = selector->data->matchAllSubjectAltNames;
    napi_value result = nullptr;
    napi_get_boolean(env, ret, &result);
    return result;
}

napi_value NapiX509CertSelector::GetAuthorityKeyIdentifier(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

     CfBlob ret = selector->data->authorityKeyIdentifier;
     if(ret.size == 0 || ret.data == nullptr) {
        LOGE("ret is nullptr!");
        return nullptr;
     }
     return ConvertBlobToUint8ArrayNapiValue(env, &ret);
}

napi_value NapiX509CertSelector::GetMinPathLenConstraint(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    int32_t ret = selector->data->minPathLenConstraint;
    napi_value result = nullptr;
    napi_create_int32(env, ret, &result);
    return result;
}

napi_value NapiX509CertSelector::GetX509Cert(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }
    auto ret = selector->data->x509Cert;
    if (!ret) {
        LOGE("x509Cert is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get x509Cert."));
        return nullptr;
    }
    //todo
    return nullptr;
}

napi_value NapiX509CertSelector::GetValidDate(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    CfBlob ret = selector->data->validDate;
     if(ret.size == 0 || ret.data == nullptr) {
        LOGE("ret is nullptr!");
        return nullptr;
     }
    napi_value result = nullptr;
    napi_create_string_utf8(env, reinterpret_cast<char *>(ret.data), ret.size, &result);
    return result;
}

napi_value NapiX509CertSelector::GetIssuer(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    CfBlob ret = selector->data->issuer;
    return ConvertBlobToUint8ArrayNapiValue(env, &ret);
}

napi_value NapiX509CertSelector::GetExtendedKeyUsage(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    CfBlobArray ret = selector->data->extendedKeyUsage;
    return ConvertBlobArrayToArrayStringNapiValue(env, &ret);
}

napi_value NapiX509CertSelector::GetKeyUsage(napi_env env, napi_callback_info info)
{
    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    CfBlob ret = selector->data->keyUsage;
    return ConvertBlobToArrayBooleanNapiValue(env, &ret);
}

napi_value NapiX509CertSelector::GetNameConstraint(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetCertPolicy(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetPrivateKeyValid(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetSerialNumber(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetSubject(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetSubjectKeyIdentifier(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetPublicKey(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::GetPublicKeyAlgID(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetSubjectAlternativeNames(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetMatchAllSubjectAltNames(napi_env env, napi_callback_info info)
{
    napi_value rhs = GetRhs(env, info);
    if (rhs == nullptr) {
        LOGE("rhs is null!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "rhs is null"));
        return nullptr;
    }

    bool boolVal = false;
    bool retCode = GetFromJSParamBoolean(env, rhs, boolVal);
    if (!retCode) {
        LOGE("get bool failed!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "get bool failed"));
        return nullptr;
    }

    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    selector->data->matchAllSubjectAltNames = boolVal;
    return nullptr;
}

napi_value NapiX509CertSelector::SetAuthorityKeyIdentifier(napi_env env, napi_callback_info info)
{
    napi_value rhs = GetRhs(env, info);
    if (rhs == nullptr) {
        LOGE("rhs is null!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "rhs is null"));
        return nullptr;
    }

    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    //Free old data if exists.
    CfBlobDataFree(&selector->data->authorityKeyIdentifier);
    CfBlob *blob = GetFromJsParamUint8Array(env, rhs);
    if (blob == nullptr) {
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "napiX509Cert is null"));
        LOGE("napiX509Cert is null!");
        return nullptr;
    }
    selector->data->authorityKeyIdentifier = *blob;
    return nullptr;
}

napi_value NapiX509CertSelector::SetMinPathLenConstraint(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetX509Cert(napi_env env, napi_callback_info info)
{
    napi_value rhs = GetRhs(env, info);
    if (rhs == nullptr) {
        LOGE("rhs is null!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "rhs is null"));
        return nullptr;
    }

    NapiX509Certificate *napiX509Cert = nullptr;
    napi_unwrap(env, rhs, reinterpret_cast<void **>(&napiX509Cert));
    if (napiX509Cert == nullptr) {
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "napiX509Cert is null"));
        LOGE("napiX509Cert is null!");
        return nullptr;
    }

    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }
    //todo: save pointer?
    selector->data->x509Cert = napiX509Cert;
    return nullptr;
}

napi_value NapiX509CertSelector::SetValidDate(napi_env env, napi_callback_info info)
{
    napi_value rhs = GetRhs(env, info);
    if (rhs == nullptr) {
        LOGE("rhs is null!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "rhs is null"));
        return nullptr;
    }

    HcfX509CertSelector *selector = GetCertSelector(env, info);
    if (selector == nullptr || selector->data == nullptr) {
        LOGE("napiSelector is nullptr!");
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "Failed to get selector."));
        return nullptr;
    }

    //Free old data if exists.
    CfBlobDataFree(&selector->data->validDate);
    std::string ret;
    bool retCode = CertGetStringFromJSParams(env, rhs, ret);
    if (!retCode) {
        napi_throw(env, CertGenerateBusinessError(env, CF_INVALID_PARAMS, "napiX509Cert is null"));
        LOGE("napiX509Cert is null!");
        return nullptr;
    }

    auto len = ret.length();
    if (len == 0) {
        LOGE("string length is 0!");
        return nullptr;
    }
    selector->data->validDate.size = len;
    selector->data->validDate.data = static_cast<uint8_t *>(HcfMalloc(len, 0));
    if (!selector->data->validDate.data) {
        napi_throw(env, CertGenerateBusinessError(env, CF_ERR_MALLOC, "malloc failed"));
        LOGE("malloc failed!");
        return nullptr;
    }
    std::copy(ret.begin(), ret.end(), selector->data->validDate.data);

    return nullptr;
}

napi_value NapiX509CertSelector::SetIssuer(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetExtendedKeyUsage(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetKeyUsage(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetNameConstraint(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetCertPolicy(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetPrivateKeyValid(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetSerialNumber(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetSubject(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetSubjectKeyIdentifier(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetPublicKey(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::SetPublicKeyAlgID(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::AddSubjectAlternativeNames(napi_env env, napi_callback_info info)
{
    return nullptr;
}

napi_value NapiX509CertSelector::Match(napi_env env, napi_callback_info info)
{
    return nullptr;
}

static napi_value X509CertSelectorConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    return thisVar;
}

void NapiX509CertSelector::DefineX509CertSelectorJSClass(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("createX509CertSelector", NapiX509CertSelector::CreateX509CertSelector),
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

    napi_property_descriptor selectorDesc[] = {
        DECLARE_NAPI_GETTER_SETTER("subjectAlternativeNames", NapiX509CertSelector::GetSubjectAlternativeNames, 
            NapiX509CertSelector::SetSubjectAlternativeNames),
        DECLARE_NAPI_GETTER_SETTER("matchAllSubjectAltNames", NapiX509CertSelector::GetMatchAllSubjectAltNames, 
            NapiX509CertSelector::SetMatchAllSubjectAltNames),
        DECLARE_NAPI_GETTER_SETTER("authorityKeyIdentifier", NapiX509CertSelector::GetAuthorityKeyIdentifier, 
            NapiX509CertSelector::SetAuthorityKeyIdentifier),
        DECLARE_NAPI_GETTER_SETTER("minPathLenConstraint", NapiX509CertSelector::GetMinPathLenConstraint, 
            NapiX509CertSelector::SetMinPathLenConstraint),
        DECLARE_NAPI_GETTER_SETTER("x509Cert", NapiX509CertSelector::GetX509Cert, NapiX509CertSelector::SetX509Cert),
        DECLARE_NAPI_GETTER_SETTER("validDate", NapiX509CertSelector::GetValidDate, NapiX509CertSelector::SetValidDate),
        DECLARE_NAPI_GETTER_SETTER("issuer", NapiX509CertSelector::GetIssuer, NapiX509CertSelector::SetIssuer),
        DECLARE_NAPI_GETTER_SETTER("extendedKeyUsage", NapiX509CertSelector::GetExtendedKeyUsage, 
            NapiX509CertSelector::SetExtendedKeyUsage),
        DECLARE_NAPI_GETTER_SETTER("keyUsage", NapiX509CertSelector::GetKeyUsage, NapiX509CertSelector::SetKeyUsage),
        DECLARE_NAPI_GETTER_SETTER("certPolicy", NapiX509CertSelector::GetCertPolicy, NapiX509CertSelector::SetCertPolicy),
        DECLARE_NAPI_GETTER_SETTER("privateKeyValid", NapiX509CertSelector::GetPrivateKeyValid, NapiX509CertSelector::SetPrivateKeyValid),
        DECLARE_NAPI_GETTER_SETTER("serialNumber", NapiX509CertSelector::GetSerialNumber, NapiX509CertSelector::SetSerialNumber),
        DECLARE_NAPI_GETTER_SETTER("subject", NapiX509CertSelector::GetSubject, NapiX509CertSelector::SetSubject),
        DECLARE_NAPI_GETTER_SETTER("certPolicy", NapiX509CertSelector::GetCertPolicy, NapiX509CertSelector::SetCertPolicy),
        DECLARE_NAPI_GETTER_SETTER("subjectKeyIdentifier", NapiX509CertSelector::GetSubjectKeyIdentifier,
            NapiX509CertSelector::SetSubjectKeyIdentifier),
        DECLARE_NAPI_GETTER_SETTER("publicKey", NapiX509CertSelector::GetPublicKey, NapiX509CertSelector::SetPublicKey),
        DECLARE_NAPI_GETTER_SETTER("publicKeyAlgID", NapiX509CertSelector::GetPublicKeyAlgID, NapiX509CertSelector::SetPublicKeyAlgID),

        DECLARE_NAPI_FUNCTION("addSubjectAlternativeNames", NapiX509CertSelector::AddSubjectAlternativeNames),
        DECLARE_NAPI_FUNCTION("match", NapiX509CertSelector::Match),
    };
    napi_value constructor = nullptr;
    napi_define_class(env, "X509CertSelector", NAPI_AUTO_LENGTH, X509CertSelectorConstructor, nullptr,
        sizeof(selectorDesc) / sizeof(selectorDesc[0]), selectorDesc, &constructor);
    napi_create_reference(env, constructor, 1, &classRef_);
}
} // namespace CertFramework
} // namespace OHOS